import Session from './fetch';
import Signal from './signal';
import { logError, logInfo } from './debugger';
import { timeCounter } from './util';
import { NOT_CONNECTE } from './peer';
import { EventEmitter2 } from 'eventemitter2';
import { find, remove, take, random } from 'lodash';

export default class Seed {
    /**
     * @param {function} finder - help others to find part
     */
    constructor(host, id, { cacheSize = 419430400, ioOpts = {} } = {}) {
        this.id = id;
        this.emitter = new EventEmitter2();
        //stores
        this.connected = [];
        this.lastUsedPeer = null;
        this.parts = [];
        this.cacheSize = cacheSize;

        this.signal = new Signal(host, ioOpts, id, this);
    }

    get partsSize() {
        let size = 0;
        for (let partInfo of this.parts) {
            size += partInfo.size;
        }
        return size;
    }

    checkPartSize() {
        while (this.partsSize > this.cacheSize) {
            this.shiftPart();
        }
    }

    shiftPart() {
        let partInfo = this.parts.shift();
        if (partInfo) return { videoId: partInfo.videoId, name: partInfo.name };
        return null;
    }

    /**
     * tell server you have which part
     * @param {*} url
     * @param {*} name
     * @param {*} buf
     */
    addPart(videoId, url, name, buf, type) {
        if (buf instanceof ArrayBuffer) {
            let partInfo = {
                videoId,
                url,
                name,
                size: buf.byteLength,
                data: buf
            };
            this.parts.push(partInfo);
            this.signal.addPart(videoId, name, type);
            this.checkPartSize();
            logInfo(this.partsSize, this.cacheSize, 4);
        }
    }

    /**
     * find part you have
     * @param {*} name
     */
    findPart(name) {
        return new Promise((resolve, reject) => {
            let found = find(this.parts, partInfo => partInfo.name === name);
            if (found) {
                resolve(found.data);
            } else {
                reject(new Error(`没有找到名字为${name}的资源`));
            }
        });
    }

    /**
     * ask server who has part
     * @param {*} partName
     */
    getPeer(videoId, partName) {
        this.signal.getPeer(videoId, partName);
    }

    /**
     * server found who has part
     * @param {*} partName
     * @param {*} seed
     */
    returnPeer(partName, seed) {
        this.emitter.emit(`part:${partName}`, seed);
    }

    /**
     * create peer connection or find connected you have
     * @param {*} id
     */
    getConnectedPeer(id) {
        if (this.connected[id]) return this.connected[id];
        let remotePeer = new Session(id, this);
        remotePeer.on('statechange', state => {
            if (state === NOT_CONNECTE) this.removeConnectedPeer(id);
        });
        this.connected[id] = remotePeer;
        this.lastUsedPeer = remotePeer;
        return remotePeer;
    }

    removeConnectedPeer(id) {
        logInfo('删除已连接PEER', 3);
    }

    /**
     * ask for an connectedPeer who has part
     * @param {*} partName
     */
    getPeerHasPart(videoId, partName, timeout) {
        return timeCounter(
            new Promise((resolve, reject) => {
                this.getPeer(videoId, partName);
                this.emitter.once(`part:${partName}`, seed => {
                    if (seed == null)
                        return reject(
                            new Error(`谁都没有 [${partName}] 这个资源`)
                        );
                    resolve(this.getConnectedPeer(seed));
                });
            }),
            timeout
        );
    }

    fetch(videoId, partName, timeout) {
        let tryLastPeer = new Promise((resolve, reject) => {
            if (this.lastUsedPeer == null)
                return reject(new Error('没有连接过P2P资源'));
            return resolve(this.lastUsedPeer.fetch(partName, timeout));
        });

        return tryLastPeer.catch(err => {
            return this.getPeerHasPart(videoId, partName, timeout).then(
                peer => {
                    return peer.fetch(partName, timeout);
                }
            );
        });
    }

    destory() {
        this.parts = [];
        this.signal.close();
    }
}
